/**
*****************************************************************************************
*     Copyright(c) 2015, Realtek Semiconductor Corporation. All rights reserved.
*****************************************************************************************
* @file     subnet_bridge_server.c
* @brief    Source file for private beacon server model.
* @details  Data types and external functions declaration.
* @author   hector_huang
* @date     2021-01-08
* @version  v1.0
* *************************************************************************************
*/
#include "mesh_config.h"

#if MESH_SBR
#include "generic_types.h"
#include "subnet_bridge_model.h"


static mesh_model_info_t sbr_server_model;


static mesh_msg_send_cause_t subnet_bridge_server_send(uint16_t dst, void *pmsg, uint16_t msg_len,
                                                       uint16_t net_key_index,
                                                       uint32_t delay_time)
{
    mesh_msg_t mesh_msg;
    mesh_msg.pmodel_info = &sbr_server_model;
    access_cfg(&mesh_msg);
    mesh_msg.pbuffer = pmsg;
    mesh_msg.msg_len = msg_len;
    mesh_msg.akf = 0;
    mesh_msg.net_key_index = net_key_index;
    mesh_msg.dst = dst;
    mesh_msg.delay_time = delay_time;
    return access_send(&mesh_msg);
}

static mesh_msg_send_cause_t subnet_bridge_status(uint16_t dst, uint16_t net_key_index,
                                                  subnet_bridge_state_t state)
{
    subnet_bridge_status_t msg;
    ACCESS_OPCODE_BYTE(msg.opcode, MESH_MSG_SUBNET_BRIDGE_STATUS);
    msg.state = state;

    return subnet_bridge_server_send(dst, &msg, sizeof(msg), net_key_index, 0);
}

static mesh_msg_send_cause_t bridging_table_status(uint16_t dst, uint16_t net_key_index,
                                                   mesh_msg_stat_t status,
                                                   uint8_t directions, uint16_t net_key_index1, uint16_t net_key_index2,
                                                   uint16_t addr1, uint16_t addr2)
{
    bridging_table_status_t msg;
    ACCESS_OPCODE_BYTE(msg.opcode, MESH_MSG_BRIDGING_TABLE_STATUS);
    msg.status = status;
    msg.directions = directions;
    msg.net_key_index1 = net_key_index1;
    msg.net_key_index2 = net_key_index2;
    msg.addr1 = addr1;
    msg.addr2 = addr2;
    return subnet_bridge_server_send(dst, &msg, sizeof(msg), net_key_index, 0);
}

static mesh_msg_send_cause_t bridged_subnets_list(uint16_t dst, uint16_t net_key_index,
                                                  subnets_filter_t filter, uint16_t filter_net_key_index, uint8_t start_index,
                                                  bridging_table_t *ptables, uint16_t table_count)
{
    uint16_t msg_len = sizeof(bridged_subnets_list_t) + 3 * table_count;
    bridged_subnets_list_t *pmsg = plt_malloc(msg_len, RAM_TYPE_DATA_ON);
    if (NULL == pmsg)
    {
        printe("bridged_subnets_list: allocate memory for bridged subnets list failed");
        return MESH_MSG_SEND_CAUSE_NO_MEMORY;
    }

    ACCESS_OPCODE_BYTE(pmsg->opcode, MESH_MSG_BRIDGED_SUBNETS_LIST);
    pmsg->filter = filter;
    pmsg->prohibited = 0;
    pmsg->net_key_index = filter_net_key_index;
    pmsg->start_index = start_index;

    for (uint16_t i = 0; i < table_count; ++i)
    {
        pmsg->bridged_subnets_list[i * 3] = ptables[i].net_key_index1_g;
        pmsg->bridged_subnets_list[i * 3 + 1] = (ptables[i].net_key_index1_g >> 8) & 0x0f;
        pmsg->bridged_subnets_list[i * 3 + 1] += ((ptables[i].net_key_index2_g & 0x0f) << 4);
        pmsg->bridged_subnets_list[i * 3 + 2] = (ptables[i].net_key_index2_g >> 4) & 0xff;
    }

    mesh_msg_send_cause_t ret = subnet_bridge_server_send(dst, pmsg, msg_len, net_key_index, 0);

    plt_free(pmsg, RAM_TYPE_DATA_ON);

    return ret;
}

static mesh_msg_send_cause_t bridging_table_list(uint16_t dst, uint16_t net_key_index,
                                                 mesh_msg_stat_t status, uint16_t net_key_index1, uint16_t net_key_index2, uint8_t start_index,
                                                 bridging_table_t *ptables, uint16_t table_count)
{
    uint16_t msg_len = sizeof(bridging_table_list_t) + 5 * table_count;
    bridging_table_list_t *pmsg = plt_malloc(msg_len, RAM_TYPE_DATA_ON);
    if (NULL == pmsg)
    {
        printe("bridging_table_list: allocate memory for bridging table list failed");
        return MESH_MSG_SEND_CAUSE_NO_MEMORY;
    }

    ACCESS_OPCODE_BYTE(pmsg->opcode, MESH_MSG_BRIDGING_TABLE_LIST);
    pmsg->status = status;
    pmsg->net_key_index1 = net_key_index1;
    pmsg->net_key_index2 = net_key_index2;
    pmsg->start_index = start_index;

    bridged_address_entry_t *pentry = (bridged_address_entry_t *)pmsg->bridged_addrs_list;

    for (uint16_t i = 0; i < table_count; ++i)
    {
        pentry->addr1 = ptables[i].addr1;
        pentry->addr2 = ptables[i].addr2;
        pentry->directions = ptables[i].directions;
        pentry ++;
    }

    mesh_msg_send_cause_t ret = subnet_bridge_server_send(dst, pmsg, msg_len, net_key_index, 0);

    plt_free(pmsg, RAM_TYPE_DATA_ON);

    return ret;
}

static mesh_msg_send_cause_t bridging_table_size_status(uint16_t dst, uint16_t net_key_index,
                                                        uint16_t bridging_table_size)
{
    bridging_table_size_status_t msg;
    ACCESS_OPCODE_BYTE(msg.opcode, MESH_MSG_BRIDGING_TABLE_SIZE_STATUS);
    msg.bridging_table_size = bridging_table_size;

    return subnet_bridge_server_send(dst, &msg, sizeof(msg), net_key_index, 0);
}

static bool bridging_table_add_handle(mesh_msg_t *pmesh_msg)
{
    uint8_t *pbuffer = pmesh_msg->pbuffer + pmesh_msg->msg_offset;
    bridging_table_add_t *pmsg = (bridging_table_add_t *)pbuffer;
    mesh_msg_stat_t status = MESH_MSG_STAT_SUCCESS;

    if ((pmsg->directions == BRIDGING_DIRECTIONS_PROHIBITED) ||
        (pmsg->directions > BRIDGING_DIRECTIONS_TWO_WAY))
    {
        printe("bridging_table_add_handle: invalid directions %d", pmsg->directions);
        return false;
    }

    if (pmsg->net_key_index1 == pmsg->net_key_index2)
    {
        printe("bridging_table_add_handle: network key index are the same value 0x%04x",
               pmsg->net_key_index1);
        return false;
    }

    if (pmsg->addr1 == pmsg->addr2)
    {
        printe("bridging_table_add_handle: addresses are the same value 0x%04x", pmsg->addr1);
        return false;
    }

    if (!MESH_IS_UNICAST_ADDR(pmsg->addr1))
    {
        printe("bridging_table_add_handle: address1 is not unicast address 0x%04x", pmsg->addr1);
        return false;
    }

    if ((pmsg->directions == BRIDGING_DIRECTIONS_ONE_WAY) &&
        ((pmsg->addr2 == MESH_UNASSIGNED_ADDR) || (pmsg->addr2 == MESH_GROUP_ADDR_ALL_NODE)))
    {
        printe("bridging_table_add_handle: address2 0x%04x is not valid in direction %d", pmsg->addr2,
               pmsg->directions);
        return false;
    }

    if ((pmsg->directions == BRIDGING_DIRECTIONS_TWO_WAY) &&
        (!MESH_IS_UNICAST_ADDR(pmsg->addr2)))
    {
        printe("bridging_table_add_handle: address2 0x%04x is not valid in direction %d", pmsg->addr2,
               pmsg->directions);
        return false;
    }

    uint16_t net_key_index = net_key_index_from_global(pmsg->net_key_index1);
    uint16_t index;
    if (mesh_node.net_key_num == net_key_index)
    {
        printe("bridging_table_add_handle: invalid net key index1 0x%04x", pmsg->net_key_index1);
        status = MESH_MSG_STAT_INVALID_NET_KEY_INDEX;
        goto END;
    }

    net_key_index = net_key_index_from_global(pmsg->net_key_index2);
    if (mesh_node.net_key_num == net_key_index)
    {
        printe("bridging_table_add_handle: invalid net key index2 0x%04x", pmsg->net_key_index2);
        status = MESH_MSG_STAT_INVALID_NET_KEY_INDEX;
        goto END;
    }

    index = subnet_bridge_table_get_index(net_key_index_from_global(pmsg->net_key_index1),
                                          net_key_index_from_global(pmsg->net_key_index2), pmsg->addr1, pmsg->addr2);
    if (index == mesh_node.bridging_table_size)
    {
        index = subnet_bridge_table_add(pmsg->directions, pmsg->net_key_index1,
                                        pmsg->net_key_index2, pmsg->addr1, pmsg->addr2);
        if (index < mesh_node.bridging_table_size)
        {
            mesh_flash_store(MESH_FLASH_PARAMS_BRIDGING_TABLE, &index);
            printi("bridging_table_add_handle: directions %d, net_key_index1 0x%04x, net_key_index2 0x%04x, addr1 0x%04x, addr2 0x%04x",
                   pmsg->directions, pmsg->net_key_index1, pmsg->net_key_index2, pmsg->addr1, pmsg->addr2);
        }
        else
        {
            /* no resource */
            status = MESH_MSG_STAT_INSUFFICIENT_RESOURCES;
        }
    }
    else
    {
        bridging_table_t *ptable = subnet_bridge_table_get_by_index(index);
        ptable->directions = pmsg->directions;
        mesh_flash_store(MESH_FLASH_PARAMS_BRIDGING_TABLE, &index);
        printi("bridging_table_add_handle: directions %d, net_key_index1 0x%04x, net_key_index2 0x%04x, addr1 0x%04x, addr2 0x%04x",
               ptable->directions, ptable->net_key_index1, ptable->net_key_index2, ptable->addr1, ptable->addr2);
    }

END:
    bridging_table_status(pmesh_msg->src, pmesh_msg->net_key_index, status,
                          pmsg->directions, pmsg->net_key_index1, pmsg->net_key_index2,
                          pmsg->addr1, pmsg->addr2);

    return true;
}

static bool bridging_table_remove_handle(mesh_msg_t *pmesh_msg)
{
    uint8_t *pbuffer = pmesh_msg->pbuffer + pmesh_msg->msg_offset;
    bridging_table_remove_t *pmsg = (bridging_table_remove_t *)pbuffer;
    mesh_msg_stat_t status = MESH_MSG_STAT_SUCCESS;

    if (MESH_IS_GROUP_ADDR(pmsg->addr1))
    {
        printe("bridging_table_remove_handle: address1 0x%04x is not valid", pmsg->addr1);
        return false;
    }

    if (pmsg->addr2 == MESH_GROUP_ADDR_ALL_NODE)
    {
        printe("bridging_table_add_handle: address2 0x%04x is not valid", pmsg->addr2);
        return false;
    }

    uint16_t net_key_index1_g = LE_EXTRN2WORD(pmsg->net_key_index) & 0x0fff;
    uint16_t net_key_index2_g = (LE_EXTRN2WORD(pmsg->net_key_index + 1) >> 4);
    if (net_key_index1_g == net_key_index2_g)
    {
        printe("bridging_table_add_handle: network key index are the same value 0x%04x", net_key_index1_g);
        return false;
    }

    if ((pmsg->addr1 == pmsg->addr2) && !MESH_IS_UNASSIGNED_ADDR(pmsg->addr1))
    {
        printe("bridging_table_add_handle: addresses are the same value 0x%04x", pmsg->addr1);
        return false;
    }

    uint16_t net_key_index = net_key_index_from_global(net_key_index1_g);
    if (mesh_node.net_key_num == net_key_index)
    {
        printe("bridging_table_remove_handle: invalid net key index1 0x%04x", net_key_index1_g);
        status = MESH_MSG_STAT_INVALID_NET_KEY_INDEX;
        goto END;
    }

    net_key_index = net_key_index_from_global(net_key_index2_g);
    if (mesh_node.net_key_num == net_key_index)
    {
        printe("bridging_table_remove_handle: invalid net key index2 0x%04x", net_key_index2_g);
        status = MESH_MSG_STAT_INVALID_NET_KEY_INDEX;
        goto END;
    }

    bridging_table_t *ptable;
    for (uint16_t i = 0; i < mesh_node.bridging_table_size; ++i)
    {
        ptable = subnet_bridge_table_get_by_index(i);
        if (!ptable->used)
        {
            continue ;
        }

        if ((ptable->net_key_index1_g == net_key_index1_g) &&
            (ptable->net_key_index2_g == net_key_index2_g))
        {
            if ((pmsg->addr1 == MESH_UNASSIGNED_ADDR) &&
                (pmsg->addr2 == MESH_UNASSIGNED_ADDR))
            {
                subnet_bridge_table_remove_by_index(i);
                mesh_flash_store(MESH_FLASH_PARAMS_BRIDGING_TABLE, &i);
            }
            else if (pmsg->addr1 == MESH_UNASSIGNED_ADDR)
            {
                if (pmsg->addr2 == ptable->addr2)
                {
                    subnet_bridge_table_remove_by_index(i);
                    mesh_flash_store(MESH_FLASH_PARAMS_BRIDGING_TABLE, &i);
                }
            }
            else if (pmsg->addr2 == MESH_UNASSIGNED_ADDR)
            {
                if (pmsg->addr1 == ptable->addr1)
                {
                    subnet_bridge_table_remove_by_index(i);
                    mesh_flash_store(MESH_FLASH_PARAMS_BRIDGING_TABLE, &i);
                }
            }
            else
            {
                if ((pmsg->addr1 == ptable->addr1) &&
                    (pmsg->addr2 == ptable->addr2))
                {
                    subnet_bridge_table_remove_by_index(i);
                    mesh_flash_store(MESH_FLASH_PARAMS_BRIDGING_TABLE, &i);
                }
            }
        }
    }

END:
    bridging_table_status(pmesh_msg->src, pmesh_msg->net_key_index, status,
                          0, net_key_index1_g, net_key_index2_g, pmsg->addr1, pmsg->addr2);

    return true;
}

static bool subnet_bridge_server_receive(mesh_msg_p pmesh_msg)
{
    bool ret = TRUE;
    uint8_t *pbuffer = pmesh_msg->pbuffer + pmesh_msg->msg_offset;
    //mesh_model_info_t *pmodel_info = pmesh_msg->pmodel_info;

    switch (pmesh_msg->access_opcode)
    {
    case MESH_MSG_SUBNET_BRIDGE_GET:
        if (pmesh_msg->msg_len == sizeof(subnet_bridge_get_t))
        {
            subnet_bridge_status(pmesh_msg->src, pmesh_msg->net_key_index,
                                 mesh_node.features.sbr);
        }
        break;
    case MESH_MSG_SUBNET_BRIDGE_SET:
        if (pmesh_msg->msg_len == sizeof(subnet_bridge_set_t))
        {
            subnet_bridge_set_t *pmsg = (subnet_bridge_set_t *)pbuffer;
            if (pmsg->state < 2)
            {
                mesh_node.features.sbr = pmsg->state;
                subnet_bridge_status(pmesh_msg->src, pmesh_msg->net_key_index,
                                     mesh_node.features.sbr);
                if (mesh_node.features.sbr == 0)
                {
                    subnet_bridge_table_clear();
                }
                printi("subnet_bridge_set: state %d", mesh_node.features.sbr);
            }
        }
        break;
    case MESH_MSG_BRIDGING_TABLE_ADD:
        if (pmesh_msg->msg_len == sizeof(bridging_table_add_t))
        {
            bridging_table_add_handle(pmesh_msg);
        }
        break;
    case MESH_MSG_BRIDGING_TABLE_REMOVE:
        if (pmesh_msg->msg_len == sizeof(bridging_table_remove_t))
        {
            bridging_table_remove_handle(pmesh_msg);
        }
        break;
    case MESH_MSG_BRIDGED_SUBNETS_GET:
        if (pmesh_msg->msg_len == sizeof(bridged_subnets_get_t))
        {
            bridged_subnets_get_t *pmsg = (bridged_subnets_get_t *)pbuffer;
            if (pmsg->prohibited != 0)
            {
                printe("bridged_subnets_get: invalid prohibited field %d", pmsg->prohibited);
                return true;
            }

            uint16_t net_key_index = net_key_index_from_global(pmsg->net_key_index);
            if (mesh_node.net_key_num == net_key_index)
            {
                printe("bridged_subnets_get: invalid net key index 0x%04x", pmsg->net_key_index);
                bridged_subnets_list(pmesh_msg->src, pmesh_msg->net_key_index, pmsg->filter, pmsg->net_key_index,
                                     pmsg->start_index, NULL, 0);
            }
            else
            {
                uint16_t count = subnet_bridge_subnets_count(pmsg->filter, net_key_index, pmsg->start_index);
                if (count == 0)
                {
                    bridged_subnets_list(pmesh_msg->src, pmesh_msg->net_key_index, pmsg->filter, pmsg->net_key_index,
                                         pmsg->start_index, NULL, 0);
                }
                else
                {
                    bridging_table_t *ptables = plt_malloc(sizeof(bridging_table_t) * count, RAM_TYPE_DATA_ON);
                    if (NULL != ptables)
                    {
                        count = subnet_bridge_subnets_list(pmsg->filter, net_key_index, pmsg->start_index, ptables);
                        bridged_subnets_list(pmesh_msg->src, pmesh_msg->net_key_index, pmsg->filter, pmsg->net_key_index,
                                             pmsg->start_index, ptables, count);
                        plt_free(ptables, RAM_TYPE_DATA_ON);
                    }
                    else
                    {
                        printe("bridged_subnets_get: allocate memory for bridged subnets list failed");
                    }
                }
            }
        }
        break;
    case MESH_MSG_BRIDGING_TABLE_GET:
        if (pmesh_msg->msg_len == sizeof(bridging_table_get_t))
        {
            mesh_msg_stat_t status = MESH_MSG_STAT_SUCCESS;
            bridging_table_get_t *pmsg = (bridging_table_get_t *)pbuffer;
            uint16_t net_key_index1_g = LE_EXTRN2WORD(pmsg->net_key_index) & 0x0fff;
            uint16_t net_key_index2_g = (LE_EXTRN2WORD(pmsg->net_key_index + 1) >> 4);
            uint16_t net_key_index1 = net_key_index_from_global(net_key_index1_g);
            bool fail = false;
            if (mesh_node.net_key_num == net_key_index1)
            {
                printe("bridging_table_get: invalid net key index1 0x%04x", net_key_index1_g);
                status = MESH_MSG_STAT_INVALID_NET_KEY_INDEX;
                fail = true;
            }

            uint16_t net_key_index2 = net_key_index_from_global(net_key_index2_g);
            if (mesh_node.net_key_num == net_key_index2)
            {
                printe("bridging_table_get: invalid net key index2 0x%04x", net_key_index2_g);
                status = MESH_MSG_STAT_INVALID_NET_KEY_INDEX;
                fail = true;
            }

            if (!fail)
            {
                uint16_t count = subnet_bridge_table_count(net_key_index1, net_key_index2, pmsg->start_index);
                if (count == 0)
                {
                    bridging_table_list(pmesh_msg->src, pmesh_msg->net_key_index, status, net_key_index1_g,
                                        net_key_index2_g, pmsg->start_index, NULL, 0);
                }
                else
                {
                    bridging_table_t *ptables = plt_malloc(sizeof(bridging_table_t) * count, RAM_TYPE_DATA_ON);
                    if (NULL != ptables)
                    {
                        count = subnet_bridge_table_list(net_key_index1, net_key_index2, pmsg->start_index, ptables);
                        bridging_table_list(pmesh_msg->src, pmesh_msg->net_key_index, status, net_key_index1_g,
                                            net_key_index2_g, pmsg->start_index, ptables, count);
                        plt_free(ptables, RAM_TYPE_DATA_ON);
                    }
                    else
                    {
                        printe("bridging_table_get: allocate memory bridging for table list failed");
                    }
                }
            }
            else
            {
                bridging_table_list(pmesh_msg->src, pmesh_msg->net_key_index, status, net_key_index1_g,
                                    net_key_index2_g, pmsg->start_index, NULL, 0);
            }
        }
        break;
    case MESH_MSG_BRIDGING_TABLE_SIZE_GET:
        if (pmesh_msg->msg_len == sizeof(bridging_table_size_get_t))
        {
            bridging_table_size_status(pmesh_msg->src, pmesh_msg->net_key_index, mesh_node.bridging_table_size);
        }
        break;
    default:
        ret = false;
        break;
    }
    return ret;
}

bool subnet_bridge_server_reg(uint8_t element_index)
{
    sbr_server_model.model_id = MESH_MODEL_SUBNET_BRIDGE_SERVER;
    sbr_server_model.model_receive = subnet_bridge_server_receive;
    return mesh_model_reg(element_index, &sbr_server_model);
}
#endif

